﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Input;
using LNUGame.Model;
using LNUGame.View.ParticleSystemBloodSplatter;
using LNUGame.Controller;

namespace LNUGame.View
{
    /// <summary>
    /// Class responsible for drawing everything related to the actual gameplay (level, player, enemies, etc.).
    /// </summary>
    class GameView : IDamageObserver
    {
        private SpriteBatch m_spriteBatch;

        private Texture2D m_texturePlayer;
        private Texture2D m_textureEnemy;
        private Texture2D m_texturePill;
        private Texture2D m_textureTile;
        private Texture2D m_textureSky;
        private Texture2D m_textureParallaxSkyline1;
        private Texture2D m_textureParallaxSkyline2;
        private Texture2D m_textureRainDrop;
        private Texture2D m_textureBloodsplatter;

        private SpriteFont m_font;

        private RainSystem m_rain = new RainSystem();
        private BloodSplatterSystem m_blood = new BloodSplatterSystem();

        private bool m_shouldShowBloodSplatter = false;
        private Vector2 m_splatterPosition = new Vector2(0.0f, 0.0f);

        private const int TILE_SIZE = 128;


        /// <summary>
        /// Main constructor.
        /// Loads all textures into memory.
        /// </summary>
        /// <param name="a_manager">The GraphicsDeviceManager to use</param>
        /// <param name="a_contentLoader">The ContentManager to use</param>
        public GameView(GraphicsDeviceManager a_manager, ContentManager a_contentLoader)
        {
            m_spriteBatch = new SpriteBatch(a_manager.GraphicsDevice);

            m_texturePlayer = a_contentLoader.Load<Texture2D>("textures/player");
            m_textureEnemy = a_contentLoader.Load<Texture2D>("textures/enemy");
            m_texturePill = a_contentLoader.Load<Texture2D>("textures/pill");
            m_textureTile = a_contentLoader.Load<Texture2D>("textures/tiles");
            m_textureSky = a_contentLoader.Load<Texture2D>("textures/sky");
            m_textureParallaxSkyline1 = a_contentLoader.Load<Texture2D>("textures/parallax_skyline_1");
            m_textureParallaxSkyline2 = a_contentLoader.Load<Texture2D>("textures/parallax_skyline_2");
            m_textureRainDrop = a_contentLoader.Load<Texture2D>("textures/raindrop");
            m_textureBloodsplatter = a_contentLoader.Load<Texture2D>("textures/bloodsplatter");

            m_font = a_contentLoader.Load<SpriteFont>("fonts/menufont");
        }

        /// <summary>
        /// Draws the entire game to the window
        /// </summary>
        /// <param name="a_elapsedTime">Time since the game was started</param>
        /// <param name="a_graphicsDevice">The GraphiicsDevice to use</param>
        /// <param name="a_camera">The camera to use</param>
        /// <param name="a_level">The current level</param>
        /// <param name="a_playerPosition">The position of the player</param>
        /// <param name="a_remainingLives">How many remaining lives the player have</param>
        /// <param name="a_playerDirection">Which direction the player is facing</param>
        /// <param name="a_enemyPositions">A list of all enemy positions</param>
        /// <param name="a_enemyDirections">A list of all enemy directions</param>
        /// <param name="a_healthpackPositions">A list of all healthpack positions</param>
        internal void DrawGame(float a_elapsedTime,
                               GraphicsDevice a_graphicsDevice,
                               Camera a_camera,
                               Level a_level,
                               Vector2 a_playerPosition,
                               int a_remainingLives,
                               Player.Direction a_playerDirection,
                               List<Vector2> a_enemyPositions,
                               List<Enemy.Direction> a_enemyDirections,
                               List<Vector2> a_healthpackPositions,
                               GameModel.GameState a_gameState
                               )
        {
            Vector2 viewPortSize = new Vector2(a_graphicsDevice.Viewport.Width, a_graphicsDevice.Viewport.Height);
            float scale = a_camera.Scale;

            a_graphicsDevice.Clear(Color.Gray);
            m_spriteBatch.Begin();

            m_spriteBatch.Draw(m_textureSky, new Rectangle(0, 0, a_graphicsDevice.Viewport.Width, a_graphicsDevice.Viewport.Height), Color.White);

            DrawParallaxSkyline(a_camera, viewPortSize);
            
            for (int x = 0; x < Level.LEVEL_WIDTH; x++){
                for (int y = 0; y < Level.LEVEL_HEIGHT; y++){
                    Vector2 viewPosition = a_camera.GetViewPosition(x, y, viewPortSize);    // Gets the part of the screen the camera points at right now
                    DrawTile(viewPosition.X, viewPosition.Y, a_level.m_tiles[x, y], scale); // Draw the part of the level that is visible
                }
            }

            foreach (Vector2 position in a_healthpackPositions)
            {
                Vector2 healthpackViewPosition = a_camera.GetViewPosition(position.X, position.Y, viewPortSize);  // Gets the player position in relation to the camera
                DrawHealthpack(healthpackViewPosition, scale / 2.0f);
            }

            for (int i = 0; i < a_enemyPositions.Count; i++)
            {
                Vector2 enemyViewPosition = a_camera.GetViewPosition(a_enemyPositions[i].X, a_enemyPositions[i].Y, viewPortSize);  // Gets the player position in relation to the camera
                DrawMovingActor(enemyViewPosition, a_enemyDirections[i], scale, m_textureEnemy, Color.White);
            }

            Vector2 playerViewPosition = a_camera.GetViewPosition(a_playerPosition.X, a_playerPosition.Y, viewPortSize);  // Gets the player position in relation to the camera

            if (m_shouldShowBloodSplatter == true)
            {
                DrawMovingActor(playerViewPosition, a_playerDirection, scale, m_texturePlayer, Color.Red);

                m_blood.DrawSplatter(m_spriteBatch, m_textureBloodsplatter, a_elapsedTime, a_camera.GetViewPosition(m_splatterPosition.X, m_splatterPosition.Y, viewPortSize));
            }
            else if(m_shouldShowBloodSplatter == false)
            {
                DrawMovingActor(playerViewPosition, a_playerDirection, scale, m_texturePlayer, Color.White);
            }

            m_rain.DrawRainDrops(m_spriteBatch, m_textureRainDrop, a_elapsedTime);

            DrawRemainingLives(a_remainingLives, scale);

            DrawGameStateText(viewPortSize, a_gameState);

            m_spriteBatch.End();
        }


        /// <summary>
        /// Draws the moving background with a parallaxing effect.
        /// Consists of two "layers" where the back layer is moving at a pace of 1/4 of the player speed
        /// and the front layer is moving at a pace of 1/2 of the player speed (read "player" as "camera").
        /// </summary>
        /// <param name="a_camera">The camera to use</param>
        /// <param name="a_viewPortSize">The size of the drwable window</param>
        private void DrawParallaxSkyline(Camera a_camera, Vector2 a_viewPortSize)
        {
            int numberOfTilesFarBackgroundSpans = m_textureParallaxSkyline1.Width / TILE_SIZE;
            int numberOfTilesNearBackgroundSpans = m_textureParallaxSkyline2.Width / TILE_SIZE;
            
            Vector2 viewPortBottomLeftPosition = a_camera.GetViewPortBottomLeftPosition(a_viewPortSize);
            Vector2 skylinePosition = Vector2.Zero;

            // Draw the far skyline
            for (int xOffset = 0; xOffset < Level.LEVEL_WIDTH; xOffset += numberOfTilesFarBackgroundSpans )
            {
                skylinePosition = a_camera.GetViewPosition(xOffset + viewPortBottomLeftPosition.X / 2, 0, a_viewPortSize);
                m_spriteBatch.Draw(m_textureParallaxSkyline1, skylinePosition, Color.White);
            }

            // Draw the near skyline
            for (int xOffset = 0; xOffset < Level.LEVEL_WIDTH; xOffset += numberOfTilesNearBackgroundSpans * 2) // For some reason I need to double the numberOfTilesNearBackgroundSpans?!
            {
                skylinePosition = a_camera.GetViewPosition(xOffset + viewPortBottomLeftPosition.X / 4, 0, a_viewPortSize);
                m_spriteBatch.Draw(m_textureParallaxSkyline2, skylinePosition, Color.White);
            }
        }


        /// <summary>
        /// Draws a single tile of the level.
        /// Detects which tile to draw and draws it at the specified position.
        /// </summary>
        /// <param name="a_x">The position to draw the tile along the x-axis</param>
        /// <param name="a_y">The position to draw the tile along the y-axis</param>
        /// <param name="a_tile">The tile to be drawn</param>
        /// <param name="a_scale">The scale to which the tile should be drawn</param>
        private void DrawTile(float a_x, float a_y, Level.Tile a_tile, float a_scale)
        {
            Rectangle sourceRectangle = new Rectangle(TILE_SIZE * (int)a_tile, 0, TILE_SIZE, TILE_SIZE); // Here we chooses which part of the tile texture we want to load
            Rectangle destinationRectangle = new Rectangle((int)a_x, (int)a_y, (int)a_scale, (int)a_scale); // This is where we want to draw the tile

            m_spriteBatch.Draw(m_textureTile, destinationRectangle, sourceRectangle, Color.White);
        }


        /// <summary>
        /// Draws an moving actor to the screen
        /// </summary>
        /// <param name="a_viewBottomCenterPosition">The position of the player</param>
        /// <param name="a_direction">The direction the player is facing</param>
        /// <param name="a_scale">The scale to which the player should be drawn</param>
        /// <param name="a_texture">Which texture to draw</param>
        /// <param name="a_color">The color overlay to use on the player</param>
        private void DrawMovingActor(Vector2 a_viewBottomCenterPosition, MovingActor.Direction a_direction, float a_scale, Texture2D a_texture, Color a_color)
        {
            Rectangle destination = new Rectangle((int)(a_viewBottomCenterPosition.X - a_scale / 2.0f), (int)(a_viewBottomCenterPosition.Y - a_scale), (int)a_scale, (int)a_scale);

            m_spriteBatch.Draw(a_texture, destination, GetCharacterSourceRectangle(a_direction, a_texture), a_color);
        }


        /// <summary>
        /// Gets the correct part of the spritesheet depedning on if the sprite is turning right or left
        /// </summary>
        /// <param name="a_direction">The direction of the sprite</param>
        /// <param name="a_texture">The texture to use</param>
        /// <returns>A Rectangle framing the correct part of the image</returns>
        private Rectangle GetCharacterSourceRectangle(MovingActor.Direction a_direction, Texture2D a_texture)
        {
            int x = 0;
            int y = 0;

            if (a_direction == MovingActor.Direction.RIGHT)
            {
                y = 0;
            }
            else if (a_direction == MovingActor.Direction.LEFT)
            {
                y = 128;
            }

            return new Rectangle(x, y, (int)(a_texture.Width), (int)(a_texture.Height / 2));
        }


        /// <summary>
        /// Draws a single healthpack to the screen
        /// </summary>
        /// <param name="a_viewBottomCenterPosition">The position of the healthpack</param>
        /// <param name="a_scale">The scale to which the healthpack should be drawn</param>
        private void DrawHealthpack(Vector2 a_viewBottomCenterPosition, float a_scale)
        {
            // This is where we want to draw the healthpack
            Rectangle destination = new Rectangle((int)(a_viewBottomCenterPosition.X - a_scale / 2.0f), (int)(a_viewBottomCenterPosition.Y - a_scale), (int)a_scale, (int)a_scale);

            m_spriteBatch.Draw(m_texturePill, destination, Color.White);
        }


        /// <summary>
        /// Draws the remaining lives of the player to the top left part of the screen
        /// </summary>
        /// <param name="a_remainingLives">The number of remaining lives</param>
        /// <param name="a_scale">The scale to use when drawing</param>
        private void DrawRemainingLives(int a_remainingLives, float a_scale)
        {
            Rectangle destination = new Rectangle(20, 10, (int)(a_scale * 0.75f), (int)(a_scale * 0.75f));
            Vector2 position = new Vector2(20.0f, 10.0f);
            for(int i = 0 ; i < a_remainingLives ; i++)
            {
                m_spriteBatch.Draw(m_texturePill, destination, null, Color.White);
                position.X += m_texturePill.Width;
                destination.X += (int)(a_scale * 0.75f);
            }
        }

        /// <summary>
        /// Is called whenever the player is hurt.
        /// This tells us to draw bloodsplatter at the specified position and that the player should have a red overlay.
        /// Generates new splatterparticles to replace those who (eventually) have already been drawn.
        /// </summary>
        /// <param name="a_splatterPosition">The position to draw the splatter on</param>
        public void PlayerIsHurtAt(Vector2 a_splatterPosition)
        {
            m_splatterPosition = a_splatterPosition;
            m_shouldShowBloodSplatter = true;
            m_blood.GenerateNewSplatterParticles();
        }

        /// <summary>
        /// Is called when the player can be hurt once again.
        /// This tells us that the bloodsplatter should no longer be drawn, and the player should return to his regular colour.
        /// </summary>
        public void PlayerCanBeHurtAgain()
        {
            m_shouldShowBloodSplatter = false;
        }


        /// <summary>
        /// Draws a suitable text for the current GameState
        /// </summary>
        /// <param name="a_viewPortSize">The size of the current viewport</param>
        /// <param name="a_GameState">The current GameState</param>
        private void DrawGameStateText(Vector2 a_viewPortSize, GameModel.GameState a_GameState)
        {
            String text = String.Empty;

            if (a_GameState == GameModel.GameState.PLAYER_DEAD)
            {
                text = "Game over!";
            }
            else if (a_GameState == GameModel.GameState.FINISHED_LEVEL)
            {
                text = "Level complete!";
            }
            else if (a_GameState == GameModel.GameState.FINISHED_LAST_LEVEL)
            {
                text = "Congratulations! You have finished the game!";
            }

            Vector2 textSize = m_font.MeasureString(text);

            float horizontalPosition = a_viewPortSize.X / 2 - textSize.X / 2;
            float verticalPosition = a_viewPortSize.Y / 2 - textSize.Y / 2;

            Vector2 textPosition = new Vector2(horizontalPosition, verticalPosition);

            m_spriteBatch.DrawString(m_font, text, textPosition, Color.White);
        }
    }
}